/**
 * @file    websocket.c
 * @author  Azolla (1228449928@qq.com)
 * @brief   websocket
 * @version 0.1
 * @date    2023-03-15
 * 
 * @copyright Copyright (c) 2023
 * */
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"  
#include "cJSON.h"
#include "sys/time.h"
#include "time.h"
 
#include "esp_event.h"
#include "esp_log.h"

#include "websocket.h"
#include "sparkai.h"

#define TAG  "websocket"

static esp_websocket_client_handle_t client = NULL;
static websocket_data_callback_t websocket_data_callback = NULL;
static bool websocket_is_start = false;
 
void websocket_register_callback(websocket_data_callback_t cb)
{
    websocket_data_callback = cb;
}

#define WSS_RECEICE_QUEUE_TASK  1   // 1: 使用队列任务处理接收到的数据！

#if WSS_RECEICE_QUEUE_TASK
static QueueHandle_t xQueue = NULL;
void websocket_receive_task(void *arg)
{
    wss_data_t data;
    while (1) {
        if (xQueueReceive(xQueue, &data, portMAX_DELAY) == pdTRUE) {
            if (websocket_data_callback) websocket_data_callback(data);
        }
    }
}
#endif

static void log_error_if_nonzero(const char *message, int error_code)
{
    if (error_code != 0) {
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}

static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
    switch (event_id) {
    case WEBSOCKET_EVENT_ERROR:          /*!< This event occurs when there are any errors during execution */
        ESP_LOGE(TAG, "WEBSOCKET_EVENT_ERROR");
        log_error_if_nonzero("HTTP status code",  data->error_handle.esp_ws_handshake_status_code);
        if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
            log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno",  data->error_handle.esp_transport_sock_errno);
        }
        break;
    case WEBSOCKET_EVENT_CONNECTED:      /*!< Once the Websocket has been connected to the server, no data exchange has been performed */
        ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
        break;
    case WEBSOCKET_EVENT_DISCONNECTED:   /*!< The connection has been disconnected */
        ESP_LOGW(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
        websocket_client_stop();
        log_error_if_nonzero("HTTP status code",  data->error_handle.esp_ws_handshake_status_code);
        if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
            log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno",  data->error_handle.esp_transport_sock_errno);
        }
        break;
    case WEBSOCKET_EVENT_DATA:           /*!< When receiving data from the server, possibly multiple portions of the packet */
        if (data->op_code == 0x08) {
            //esp_log_buffer_hex("data", data->data_ptr, data->data_len);
            ESP_LOGW(TAG, "wss closed message: %.*s", data->data_len, (char *)data->data_ptr);
            //ESP_LOGW(TAG, "Received closed message with code= %d", 256 * data->data_ptr[0] + data->data_ptr[1]);
        } else if (data->op_code == 0x01) {
            //ESP_LOGI(TAG, "Received = %.*s", data->data_len, (char *)data->data_ptr);
            //ESP_LOGI(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
            #if WSS_RECEICE_QUEUE_TASK
            // ESP_LOGI(TAG, "Received = %d", data->data_len);
            static wss_data_t wdata = { 0 };
            if (wdata.value == NULL) wdata.value = heap_caps_malloc(1024, (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
            assert(wdata.value);
            wdata.size = data->data_len;
            memcpy(wdata.value, data->data_ptr, data->data_len);
            xQueueSend(xQueue, &wdata, (TickType_t)0); 
            #else
            wss_data_t wdata;
            wdata.value = data->data_ptr;
            wdata.size  = data->data_len;
            if (websocket_data_callback) websocket_data_callback(wdata);
            #endif
        } else {
            //ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
            //ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
        }
        break;
    case WEBSOCKET_EVENT_CLOSED:         /*!< The connection has been closed cleanly */
        ESP_LOGW(TAG, "WEBSOCKET_EVENT_CLOSED");
        websocket_is_start = false;
        break;
    case WEBSOCKET_EVENT_BEFORE_CONNECT: /*!< The event occurs before connecting */
    case WEBSOCKET_EVENT_BEGIN:          /*!< The event occurs once after thread creation, before event loop */
    case WEBSOCKET_EVENT_FINISH:         /*!< The event occurs once after event loop, before thread destruction */
    default:
        break;
    }
}

bool websocket_client_is_connected(void)
{
    if (client == NULL) return false;
    return esp_websocket_client_is_connected(client);
}

void websocket_client_stop(void)
{
    if (websocket_is_start == true) {
        websocket_is_start = false;
        esp_websocket_client_stop(client);
    }
}

void websocket_client_start(void)
{
    if (websocket_is_start == false) {
        websocket_is_start = true;
        esp_websocket_client_start(client);
    }
}

void websocket_client_close(uint32_t timeout)
{
    esp_websocket_client_close(client, timeout);
    // esp_websocket_client_close_with_code(client, 0x03e8, "close", 5, timeout);
}

void websocket_client_set_url(const char *uri)
{
    esp_websocket_client_set_uri(client, uri);
}

int websocket_client_send_text(const char *data, int len, TickType_t timeout)
{
    return esp_websocket_client_send_text(client, data, len, timeout);
}

// websocket init...
// websocket Tools:  https://wwa.lanzoui.com/b00n4d3yd
void websocket_init(void)
{
    esp_log_level_set("websocket_client", ESP_LOG_WARN);
   
    esp_websocket_client_config_t websocket_cfg = { };
 #if CONFIG_WS_OVER_TLS_MUTUAL_AUTH
    /* Configuring client certificates for mutual authentification */
    // root.crt client.key client.crt
    extern const char cacert_pem_start[]  asm("_binary_root_crt_start");
    extern const char cacert_pem_end[]    asm("_binary_root_crt_end");
    extern const char prvtkey_pem_start[] asm("_binary_client_key_start");
    extern const char prvtkey_pem_end[]   asm("_binary_client_key_end");
    extern const char client_pem_start[]   asm("_binary_client_crt_start");
    extern const char client_pem_end[]   asm("_binary_client_crt_end");
    websocket_cfg.cert_pem = cacert_pem_start;
    websocket_cfg.cert_len = cacert_pem_end - cacert_pem_start;
    websocket_cfg.client_key = prvtkey_pem_start;
    websocket_cfg.client_key_len = prvtkey_pem_end - prvtkey_pem_start;
    websocket_cfg.client_cert = client_pem_start;
    websocket_cfg.client_cert_len = client_pem_end - client_pem_start;
#endif
#if CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK
    websocket_cfg.skip_cert_common_name_check = true;
#endif
    websocket_cfg.task_stack = 8 * 1024;
    websocket_cfg.buffer_size = 2048;
    websocket_cfg.ping_interval_sec = 30;
    websocket_cfg.network_timeout_ms = 5000;
    websocket_cfg.reconnect_timeout_ms = 10000;
    websocket_cfg.keep_alive_enable = true;
    websocket_cfg.disable_auto_reconnect = true;
    websocket_cfg.transport = WEBSOCKET_TRANSPORT_OVER_TCP;
    websocket_cfg.uri = "ws://spark-api.xf-yun.com/v1.1/chat"; //"ws://192.168.0.116:8848";  // wss://192.168.2.103:8848
    //ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
    
    client = esp_websocket_client_init(&websocket_cfg);
    esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
    //esp_websocket_client_start(client);
#if WSS_RECEICE_QUEUE_TASK  
    xQueue = xQueueCreate(2, sizeof(wss_data_t));
    xTaskCreatePinnedToCore(websocket_receive_task, "websocket_receive", 6 * 1024, NULL, 10, NULL, APP_CPU_NUM); 
#endif

#if 0  // test...
    char data[32]; int i = 0;
    while (true) {
        vTaskDelay(3000 / portTICK_PERIOD_MS);
        if (esp_websocket_client_is_connected(client)) {
            #if 0
            int len = sprintf(data, "hello %04d", i++);
            ESP_LOGI(TAG, "Sending %s", data);
            esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
            #else 
            if (i == 0) {
                i++;
                sparkai_message("你会做什么");
            } 
            #endif
        } else {
            ESP_LOGW(TAG, "not connected....");
        }
    }
#endif
}

